﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace GameComponents
{
    public enum CustomButtonState { Held = 0, Null, Pressed, Released }
    
    public struct ButtonResult
    {
        const float JOYSTICK_SPEED = 20;
        const float THRESH_JOYSTICK = .125f;

        public Vector2 RightJoyStick;
        public Vector2 LeftJoyStick;

        public CustomButtonState ButtonStart;
        public CustomButtonState ButtonBack;

        public CustomButtonState ButtonA;
        public CustomButtonState ButtonB;
        public CustomButtonState ButtonX;
        public CustomButtonState ButtonY;

        public CustomButtonState ButtonRT;
        public CustomButtonState ButtonRB;

        public CustomButtonState ButtonLT;
        public CustomButtonState ButtonLB;

        public CustomButtonState ButtonRightStick;
        public CustomButtonState ButtonLeftStick;

        public ButtonResult(GamePadState oldPadState, GamePadState newPadState)
        {
            LeftJoyStick = newPadState.ThumbSticks.Left;
            RightJoyStick = newPadState.ThumbSticks.Right;

            #region START, BACK
            // Start
            if (oldPadState.IsButtonUp(Buttons.Start))
            {
                if (newPadState.IsButtonDown(Buttons.Start))
                { ButtonStart = CustomButtonState.Pressed; }
                else { ButtonStart = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.Start))
                { ButtonStart = CustomButtonState.Held; }
                else { ButtonStart = CustomButtonState.Released; }
            }

            // Back
            if (oldPadState.IsButtonUp(Buttons.Back))
            {
                if (newPadState.IsButtonDown(Buttons.Back))
                { ButtonBack = CustomButtonState.Pressed; }
                else { ButtonBack = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.Back))
                { ButtonBack = CustomButtonState.Held; }
                else { ButtonBack = CustomButtonState.Released; }
            }
            #endregion

            #region A, B, X, Y
            // A
            if (oldPadState.IsButtonUp(Buttons.A))
            {
                if (newPadState.IsButtonDown(Buttons.A))
                { ButtonA = CustomButtonState.Pressed; }
                else { ButtonA = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.A))
                { ButtonA = CustomButtonState.Held; }
                else { ButtonA = CustomButtonState.Released; }
            }

            // B
            if (oldPadState.IsButtonUp(Buttons.B))
            {
                if (newPadState.IsButtonDown(Buttons.B))
                { ButtonB = CustomButtonState.Pressed; }
                else { ButtonB = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.B))
                { ButtonB = CustomButtonState.Held; }
                else { ButtonB = CustomButtonState.Released; }
            }

            // X
            if (oldPadState.IsButtonUp(Buttons.X))
            {
                if (newPadState.IsButtonDown(Buttons.X))
                { ButtonX = CustomButtonState.Pressed; }
                else { ButtonX = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.X))
                { ButtonX = CustomButtonState.Held; }
                else { ButtonX = CustomButtonState.Released; }
            }

            // Y
            if (oldPadState.IsButtonUp(Buttons.Y))
            {
                if (newPadState.IsButtonDown(Buttons.Y))
                { ButtonY = CustomButtonState.Pressed; }
                else { ButtonY = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.Y))
                { ButtonY = CustomButtonState.Held; }
                else { ButtonY = CustomButtonState.Released; }
            }
            #endregion

            #region RIGHT TRIGGER, RIGHT BUMPER
            // Right Trigger
            if (oldPadState.IsButtonUp(Buttons.RightTrigger))
            {
                if (newPadState.IsButtonDown(Buttons.RightTrigger))
                { ButtonRT = CustomButtonState.Pressed; }
                else { ButtonRT = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.RightTrigger))
                { ButtonRT = CustomButtonState.Held; }
                else { ButtonRT = CustomButtonState.Released; }
            }

            // Right Bumper
            if (oldPadState.IsButtonUp(Buttons.RightShoulder))
            {
                if (newPadState.IsButtonDown(Buttons.RightShoulder))
                { ButtonRB = CustomButtonState.Pressed; }
                else { ButtonRB = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.RightShoulder))
                { ButtonRB = CustomButtonState.Held; }
                else { ButtonRB = CustomButtonState.Released; }
            }
            #endregion

            #region LEFT TRIGGER, LEFT BUMPER
            // Right Trigger
            if (oldPadState.IsButtonUp(Buttons.LeftTrigger))
            {
                if (newPadState.IsButtonDown(Buttons.LeftTrigger))
                { ButtonLT = CustomButtonState.Pressed; }
                else { ButtonLT = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.LeftTrigger))
                { ButtonLT = CustomButtonState.Held; }
                else { ButtonLT = CustomButtonState.Released; }
            }

            // Left Bumper
            if (oldPadState.IsButtonUp(Buttons.LeftShoulder))
            {
                if (newPadState.IsButtonDown(Buttons.LeftShoulder))
                { ButtonLB = CustomButtonState.Pressed; }
                else { ButtonLB = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.LeftShoulder))
                { ButtonLB = CustomButtonState.Held; }
                else { ButtonLB = CustomButtonState.Released; }
            }
            #endregion

            #region LEFT STICK IN, RIGHT STICK IN
            // Left Stick
            if (oldPadState.IsButtonUp(Buttons.LeftStick))
            {
                if (newPadState.IsButtonDown(Buttons.LeftStick))
                { ButtonLeftStick = CustomButtonState.Pressed; }
                else { ButtonLeftStick = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.LeftStick))
                { ButtonLeftStick = CustomButtonState.Held; }
                else { ButtonLeftStick = CustomButtonState.Released; }
            }

            // Right Stick 
            if (oldPadState.IsButtonUp(Buttons.RightStick))
            {
                if (newPadState.IsButtonDown(Buttons.RightStick))
                { ButtonRightStick = CustomButtonState.Pressed; }
                else { ButtonRightStick = CustomButtonState.Null; }
            }
            else
            {
                if (newPadState.IsButtonDown(Buttons.RightStick))
                { ButtonRightStick = CustomButtonState.Held; }
                else { ButtonRightStick = CustomButtonState.Released; }
            }
            #endregion
        }

        public ButtonResult(KeyboardState oldKeyState, MouseState oldMouse, KeyboardState newKeyState, MouseState newMouse)
        {
            #region RIGHT STICK, LEFT STICK - KEYS: W, S, A, D, MOUSE (LOOK)
            RightJoyStick = JOYSTICK_SPEED * (new Vector2((float)(newMouse.X - oldMouse.X) / 1280.0f, -(float)(newMouse.Y - oldMouse.Y) / 720.0f));
            LeftJoyStick = Vector2.Zero;

            if (newKeyState.IsKeyDown(Keys.W))
            { LeftJoyStick += Vector2.UnitY; }
            if (newKeyState.IsKeyDown(Keys.S))
            { LeftJoyStick -= Vector2.UnitY; }

            if (newKeyState.IsKeyDown(Keys.A))
            { LeftJoyStick -= Vector2.UnitX; }
            if (newKeyState.IsKeyDown(Keys.D))
            { LeftJoyStick += Vector2.UnitX; }
            #endregion

            #region START, BACK - KEYS: ENTER, ESCAPE
            // Start - Enter
            if (oldKeyState.IsKeyUp(Keys.Enter))
            {
                if (newKeyState.IsKeyDown(Keys.Enter))
                { ButtonStart = CustomButtonState.Pressed; }
                else { ButtonStart = CustomButtonState.Null; }
            }
            else
            {
                if (newKeyState.IsKeyDown(Keys.Enter))
                { ButtonStart = CustomButtonState.Held; }
                else { ButtonStart = CustomButtonState.Released; }
            }

            // Back - Escape
            if (oldKeyState.IsKeyUp(Keys.Escape))
            {
                if (newKeyState.IsKeyDown(Keys.Escape))
                { ButtonBack = CustomButtonState.Pressed; }
                else { ButtonBack = CustomButtonState.Null; }
            }
            else
            {
                if (newKeyState.IsKeyDown(Keys.Escape))
                { ButtonBack = CustomButtonState.Held; }
                else { ButtonBack = CustomButtonState.Released; }
            }
            #endregion

            #region A, B, X, Y - KEYS: SPACE, Q, R, E
            // A - Space
            if (oldKeyState.IsKeyUp(Keys.Space))
            {
                if (newKeyState.IsKeyDown(Keys.Space))
                { ButtonA = CustomButtonState.Pressed; }
                else { ButtonA = CustomButtonState.Null; }
            }
            else
            {
                if (newKeyState.IsKeyDown(Keys.Space))
                { ButtonA = CustomButtonState.Held; }
                else { ButtonA = CustomButtonState.Released; }
            }

            // B - Q
            if (oldKeyState.IsKeyUp(Keys.Q))
            {
                if (newKeyState.IsKeyDown(Keys.Q))
                { ButtonB = CustomButtonState.Pressed; }
                else { ButtonB = CustomButtonState.Null; }
            }
            else
            {
                if (newKeyState.IsKeyDown(Keys.Q))
                { ButtonB = CustomButtonState.Held; }
                else { ButtonB = CustomButtonState.Released; }
            }

            // R - X
            if (oldKeyState.IsKeyUp(Keys.R))
            {
                if (newKeyState.IsKeyDown(Keys.R))
                { ButtonX = CustomButtonState.Pressed; }
                else { ButtonX = CustomButtonState.Null; }
            }
            else
            {
                if (newKeyState.IsKeyDown(Keys.R))
                { ButtonX = CustomButtonState.Held; }
                else { ButtonX = CustomButtonState.Released; }
            }

            // E
            if (oldKeyState.IsKeyUp(Keys.E))
            {
                if (newKeyState.IsKeyDown(Keys.E))
                { ButtonY = CustomButtonState.Pressed; }
                else { ButtonY = CustomButtonState.Null; }
            }
            else
            {
                if (newKeyState.IsKeyDown(Keys.E))
                { ButtonY = CustomButtonState.Held; }
                else { ButtonY = CustomButtonState.Released; }
            }
            #endregion

            #region RIGHT TRIGGER, RIGHT BUMPER - KEYS: MOUSE LEFT, C
            // Right Trigger - Left Mouse
            if (oldMouse.LeftButton == ButtonState.Released)
            {
                if (newMouse.LeftButton == ButtonState.Pressed)
                { ButtonRT = CustomButtonState.Pressed; }
                else { ButtonRT = CustomButtonState.Null; }
            }
            else
            {
                if (newMouse.LeftButton == ButtonState.Pressed)
                { ButtonRT = CustomButtonState.Held; }
                else { ButtonRT = CustomButtonState.Released; }
            }

            // Right Bumper - C
            if (oldKeyState.IsKeyUp(Keys.C))
            {
                if (newKeyState.IsKeyDown(Keys.C))
                { ButtonRB = CustomButtonState.Pressed; }
                else { ButtonRB = CustomButtonState.Null; }
            }
            else
            {
                if (newKeyState.IsKeyDown(Keys.C))
                { ButtonRB = CustomButtonState.Held; }
                else { ButtonRB = CustomButtonState.Released; }
            }
            #endregion

            #region LEFT TRIGGER, LEFT BUMPER - KEYS: MOUSE RIGHT, LEFT ALT
            // Left Trigger - Mouse Right
            if (oldMouse.RightButton == ButtonState.Released)
            {
                if (newMouse.RightButton == ButtonState.Pressed)
                { ButtonLT = CustomButtonState.Pressed; }
                else { ButtonLT = CustomButtonState.Null; }
            }
            else
            {
                if (newMouse.RightButton == ButtonState.Pressed)
                { ButtonLT = CustomButtonState.Held; }
                else { ButtonLT = CustomButtonState.Released; }
            }

            // Left Bumper - Left Alt
            if (oldKeyState.IsKeyUp(Keys.LeftAlt))
            {
                if (newKeyState.IsKeyDown(Keys.LeftAlt))
                { ButtonLB = CustomButtonState.Pressed; }
                else { ButtonLB = CustomButtonState.Null; }
            }
            else
            {
                if (newKeyState.IsKeyDown(Keys.LeftAlt))
                { ButtonLB = CustomButtonState.Held; }
                else { ButtonLB = CustomButtonState.Released; }
            }
            #endregion

            #region LEFT STICK IN, RIGHT STICK IN - KEYS: LEFT SHIFT, V
            // Left Stick
            if (oldKeyState.IsKeyUp(Keys.LeftShift))
            {
                if (newKeyState.IsKeyDown(Keys.LeftShift))
                { ButtonLeftStick = CustomButtonState.Pressed; }
                else { ButtonLeftStick = CustomButtonState.Null; }
            }
            else
            {
                if (newKeyState.IsKeyDown(Keys.LeftShift))
                { ButtonLeftStick = CustomButtonState.Held; }
                else { ButtonLeftStick = CustomButtonState.Released; }
            }

            // Right Stick - V
            if (oldKeyState.IsKeyUp(Keys.V))
            {
                if (newKeyState.IsKeyDown(Keys.V))
                { ButtonRightStick = CustomButtonState.Pressed; }
                else { ButtonRightStick = CustomButtonState.Null; }
            }
            else
            {
                if (newKeyState.IsKeyDown(Keys.V))
                { ButtonRightStick = CustomButtonState.Held; }
                else { ButtonRightStick = CustomButtonState.Released; }
            }
            #endregion
        }
    }

    public class Camera
    {
        private Vector3 position;
        private Vector3 forward;
        private Vector3 up;

        private bool moveInProgress = false;
        private Vector3 moveBuffer = Vector3.Zero;

        private bool lookInProgress = false;
        private Vector3 lookBuffer = Vector3.Zero;

        public float LookSpeed = 6;
        public float MoveSpeed = 50;

        private bool isClampX = false;
        private float clampX = 0;

        private bool isClampY = false;
        private float clampY = 0;

        private bool isClampZ = false;
        private float clampZ = 0;

        public Camera(Vector3 newPos, Vector3 newForward, Vector3 newUp)
        {
            position = newPos;
            forward = newForward;
            up = newUp;
        }

        // ***** ACCESSORS *****

        public Vector3 Left { get { return Vector3.Normalize(-Vector3.Cross(forward, up)); } }
        public Vector3 Right { get { return Vector3.Normalize(Vector3.Cross(forward, up)); } }
        public Vector3 Position { get { return position; } set { position = value; } }
        public Vector3 Forward { get { return forward; } set { forward = value; } }
        public Vector3 Up { get { return up; } set { up = value; } }
        public Matrix LookAt { get { return Matrix.CreateLookAt(position, position + forward, up); } }

        // ***** USER METHODS *****

        public void ClampX(float newClamp)
        { clampX = newClamp; isClampX = true; }

        public void ClampY(float newClamp)
        { clampY = newClamp; isClampY = true; }

        public void ClampZ(float newClamp)
        { clampZ = newClamp; isClampZ = true; }

        public void Fly(float ticks)
        {
            moveBuffer += ticks * MoveSpeed * up;
            moveInProgress = true;
        }
        public void Strafe(float ticks)
        {
            moveBuffer += ticks * MoveSpeed * Right;
            moveInProgress = true;
        }
        public void Stride(float ticks)
        {
            moveBuffer += ticks * MoveSpeed * forward;
            moveInProgress = true;
        }

        public void LookHorizontal(float ticks)
        {
            lookBuffer.X += ticks * LookSpeed;
            lookInProgress = true;
        }
        public void LookVertical(float ticks)
        {
            lookBuffer.Y += ticks * LookSpeed;
            lookInProgress = true;
        }
        public void LookTilt(float ticks)
        {
            lookBuffer.Z += ticks * LookSpeed;
            lookInProgress = true;
        }

        public void LookAtTarget(Vector3 targetPos)
        {
            // if rotation required
            if (Vector3.Dot(forward, Vector3.Normalize(targetPos - position)) < .95f)
            {
                Matrix rotMat = Matrix.CreateBillboard(position, targetPos, up, forward);
                forward = Vector3.TransformNormal(forward, rotMat);
                up = Vector3.TransformNormal(up, rotMat);
            }
        }

        public void UnClampX()
        { clampX = 0; isClampX = false; }

        public void UnClampY()
        { clampY = 0; isClampY = false; }

        public void UnClampZ()
        { clampZ = 0; isClampZ = false; }

        public void Update(float tSeconds)
        {
            if (moveInProgress)
            {
                position += moveBuffer * tSeconds;

                moveBuffer = Vector3.Zero;
                moveInProgress = false;
            }

            if (lookInProgress)
            {
                Matrix rotMat = Matrix.CreateFromAxisAngle(up, -lookBuffer.X * tSeconds) * Matrix.CreateFromAxisAngle(Right, lookBuffer.Y * tSeconds)
                    * Matrix.CreateFromAxisAngle(forward, lookBuffer.Z * tSeconds);
                forward = Vector3.TransformNormal(forward, rotMat);
                up = Vector3.TransformNormal(up, rotMat);

                lookInProgress = false;
                lookBuffer = Vector3.Zero;
            }

            if (isClampX) { position.X += LookSpeed * (clampX - position.X) / clampX; }
            if (isClampY) { position.Y += LookSpeed * (clampY - position.Y) / clampY; }
            if (isClampZ) { position.Z += LookSpeed * (clampZ - position.Z) / clampZ; }
        }
    }

    public struct Vector2Int
    {
        public int X;
        public int Y;

        public Vector2Int(int newX, int newY)
        { X = newX; Y = newY; }
    }
}
